-- 2022-8-10

--[[ 15.0

    在处理数据文件时 写数据通常比读入数据简单得多
    当向一个文件中写时 我们拥有绝对的控制权 但是 当从一个文件中读时 我们并不知道会读到什么东西
    一个健壮的程序除了能够处理一个合法文件中所包含的所有类型的数据外 还应该能够优雅的处理错误的文件
    因此 编写一个健壮的处理输入的程序总是比较困难的
    在本章中 我们会学习如何使用Lua语言 通过简单的将数据以恰当的格式写入到文件中来程序中剔除不必要的读取数据的代码
    更确切的说 我们将学习如何像Lua程序在运行中写入数据那样 在运行时重建数据

    Lua语言自1993年发布一来 其主要用途之一就是描述数据
    在那个年代 主要的文本数据描述语言之一是SGML 对于很多人来说 SGML既臃肿又复杂
    在1998年 有些人将其简化成了XML 但以我们的眼光看仍然臃肿又复杂
    有些人跟我们的观点一直 进而在2001年开发了JSON
    JSON基于Javascript 类似于一直精简过的Lua语言数据文件
    一方面 JSON的一大优势在于它是国际标准 包括Lua语言在内的多种语言都具有操作JSON文件的标准库
    另一方面 Lua语言数据文件的读取更加容易和灵活

    使用一门全功能的编程语言来描述数据确实非常灵活 但也会带来两个问题
    问题之一在于安全性 这是因为数据文件能够肆意的在我们的程序中运行 我们可以通过在沙盒中运行程序来解决这个问题

    另一个问题是性能问题 Lua语言不仅运行的快 编译也很快
]]

---[==[ 15.1 数据文件

    -- 对于文件格式来说 表构造器提供了一种有趣的替代方式
    -- 只需在写入数据时做一点额外的工作 就能使读取数据变得容易 这种技巧就是将数据文件写成Lua代码 当这些代码运行时 程序也就把数据重建了
    -- 使用表构造器时 这些代码看上去会非常像是一个普通的数据文件

    -- 下面通过一个示例来进一步展示处理数据文件的方式 
    -- 如果数据文件使用的是诸如CSV(comma-seprated value 逗号分隔值)或XML等预先定义好的格式 那么我们能够选择的方法不多
    -- 不过 如果处理的是处于自身需求而创建的数据文件 那么就可以将Lua语言的构造器用于格式定义
    -- 此时 我们把每条数据记录表示为一个Lua构造器
    -- 这样 原来类似
    -- Donald E. Knuth,literate Programming,CSLI,1992
    -- 的数据文件就可以改为
    Entry{
        "Donald E. Knuth",
        "literate Programming",
        "CSLI",
        1992
    }
    -- 请注意 Entry{code}与Entry({code}) 是相同的 后者以表为唯一的参数来调用函数Entry
    -- 因此 上面这段数据也是一个Lua程序
    -- 当需要读取该文件时 我们只需要定义一个合法的Entry 然后运行这个程序即可
    -- 例如 以下的代码用于计算某个数据文件中数据条目的个数
    local count = 0
    function Entry()
        count = count + 1
    end
    dofile("data")
    print("number of entries:"..count)
    -- 下面的程序获取某个数据文件中所有作者的姓名 然后打印出这些姓名
    local authors = {} -- 保存作者姓名的集合
    function Entry(b)
        authors[b[1]] = true
    end
    dofile("data")
    for name in pairs(authors) do
        print(name)
    end
    -- 请注意 上述的代码段中使用了事件驱动(event-driven)的方式:函数Entry作为一个回调函数会在函数dofile处理数据文件中的每个条目时被调用
    -- 当文件的大小并不是太大时 可以使用键值对的表示方法
    Entry{
        author = "Donald E. Knuth",
        title = "literate Programming",
        publisher = "CSLI",
        year = 1992
    }
    -- 这种格式是所谓的自描述数据格式 其中数据的每个字段都具有一个对应其含义的简略描述
    -- 自描述数据比CSV或其他压缩格式的可读性更好(至少看上去如此)
    -- 同时 当需要修改时 自描述数据也易于手工编辑
    -- 此外 自描述数据还允许我们在不改变数据文件的情况下对基本数据格式进行细微的修改
    -- 例如 当我们想要增加一个新字段时 只需对读取数据文件的程序稍加修改 使其在新字段不存在时使用默认值
    -- 使用键值对格式时 获取作者姓名的程序将变为
    local authors = {} -- 保存作者姓名的集合
    function Entry(b)
        authors[b.author] = true
    end
    dofile("data")
    for name in pairs(authors) do
        print(name)
    end
    -- 此时 字段的次序就无关紧要了 即使有些记录没有作者字段 我们也只需修改Entry函数
    function Entry(b)
        authors[b.author or "unknown"] = true
    end
--]==]